第九章 开发工具

本章节主要介绍了各种开发用的工具,比如:

其中有些工具需要下载安装,在我的 CentOS 7 上面,需要执行这些命令:

sudo yum install rcs.x86_64
sudo yum install cvs.x86_64
sudo yum install patch.x86_64

多个源文件带来的问题

对大型程序来说,如果每次都编译所有的文件来构建应用程序,就会有明显的问题。

有时候,无需重新编译所有文件,而只需要编译需要编译的文件。 make 命令就是用于解决这个问题的,它会在必要时重新编译所有受改动影响的源文件。

make 命令和 makefile 文件

必须为 make 命令提供一个文件,告诉它应用程序如何构造,这个文件称为 makefile 。

makefile 文件一般和项目的其他源文件放在同一目录下,如果管理的是一个大项目,可以用多个 makefile 文件来分别管理项目的不同部分。

makefile 的语法

makefile 文件由一组依赖关系和规则构成。每个依赖关系由一个目标和一组该目标所依赖的源文件组成。而规则描述了如何通过这些依赖文件创建目标。一般来说,目标是一个单独的可执行文件。

Note

由于存在内置规则,所以有时候可以省略规则的编写。

make 命令读取 makefile 文件,确定目标文件或者要创建的文件,然后比较该目标所依赖的源文件的日期以决定该采用哪条规则来构造目标。

make 命令的选项和参数

为了指示 make 命令创建一个特定的目标,可以把该目标的名字作为 make 命令的一个参数,如果不加参数, make 命令就试图在 makefile 中查找第一个目标。

依赖关系

如:

myapp: main.o 2.o 3.o

这条依赖关系的意思是: myapp 程序依赖于 main.o 2.o 3.o 这几个文件。

如果想一次创建多个文件,可以利用伪目标 all :

all: myapp myapp.1

规则

规则定义了目标的创建方式。规则的开头必须用制表符。

main.o: main.c a.h  # 这是依赖关系(目标文件:依赖的文件)
    gcc -c main.c   # 这是一条(创建目标的)规则

makefile 文件中的注释

可以使用 # 号来注释,注释一直延续到一行的结束。

makefile 文件中的宏

有时候用宏可以更好的编写 makefile ,并可以适配内置规则(即为内置规则设置宏的值)。

定义宏的方法是: MACRONAME=value

引用宏的方法是: $(MACRONAME) 或 ${MACRONAME}

可以在命令行下面使用宏,从而覆盖文件中的宏定义:

$ make CC=c89

cmake 命令内置了一些特殊的宏定义,通过使用它们可以让 makefile 文件变得更加简洁。这些宏在使用前才展开,所以它们的含义会随着 makefile 文件的处理进展而发生变化。

定义
$? 当前目标所依赖的文件列表中比当前目标文件还要新的文件
$@ 当前目标的名字
$< 当前依赖文件的名字
$* 不包括后缀名的当前依赖文件的名字

另外,还有两个特殊字符,用在命令之前:

多个目标

可以给 makefile 增加多个目标,然后运行 make 命令时显示的指定它。比如,可以增加一个 clean 目标,用于清理掉中间文件:

clean:
    -rm -f *.o

没有依赖文件的话,就代表目标永远都是过时的,每次都要执行规则。

规则除了是命令,还可以是一条 shell 脚本,为了执行多条语句,可以用 ; 号将他们连接起来组成一条语句。为了分行显示,就用反斜杠连接它们。

内置规则

内置规则可以帮助简化 makefile 文件。

比如,对于一个 foo.c 文件,可以直接执行 make foo 来构建可执行程序, make 的内置规则知道如何编译它。

使用命令 make -p 可以查看所有的内置规则。

后缀和模式规则

当给出带有某个特定后缀名的文件时, make 命令知道应该用哪个规则来创建带有另一个不同后缀名的文件。最常见的一条规则是用于从一个 .c 文件创建出一个 .o 文件。

若想要增加一条新的后缀规则,首先需要在 makefile 文件中增加一行语句,告诉 make 命令这个新的后缀名。然后即可用这个新的后缀名来定义规则:

.<old_suffix>.<new_suffix>

比如,可以用这样的新规则将 .cpp 文件编译为 .o 文件:

.SUFFIXES:  .cpp
.cpp.o:
    $(CC) -xc++ $(CFLAGS) -I$(INCLUDES) -c $<

用 make 管理函数库

用于管理函数库的语法是 lib(file.o) ,其含义是目标文件 file.o 是存储在函数库 lib.a 中的。 make 命令用一个内置规则来管理函数库:

.c.a:
    $(CC) -c $(CFLAGS) $<
    $(AR) $(ARFLAGS) $@ $*.o

高级主题: makefile 文件和子目录

把文件分到多个目录中,再用 make 命令编译整个项目。可以用的方法有两个。

其一是在子目录中也写一个 makefile ,用于处理该目录下面的源文件。在主目录中,使用下面的方法调用子目录的 makefile :

mylib.a
    (cd libdir;$(MAKE))

每当 make 命令调用这条规则,它将切换到 libdir 目录,然后调用新的 make 命令来在此目录执行构建。

第二种方法是使用新的特殊宏,和新的一个后缀规则,详细见书本介绍。

GNU make 和 gcc

GNU make 和 gcc 提供了两个有趣的选项:

对于源代码中的头文件,如"foo.h",那么就会认为它是一个依赖。但如果是<foo.h>就不会,因为尖括号被认为是系统头文件,通常不会修改的。

源代码控制

RCS

RCS 提供了许多用于管理源文件的命令。它能够跟踪并记录下源文件的每一次改动,并将这些改动都记录到一个文件中去,通过此文件可以重建出任何一个以前的版本,而且保有注释信息。

RCS 只保存版本之间的不同之处,所以它非常节省存储空间。

rcs 命令

初始化一个文件的 RCS 控制:

$ rcs -i important.c

这会生成一个额外的只读文件:important.c,v

Note

如果当前的目录下有一个 RCS 目录,那么此命令就会把生成的只读文件放到这个目录里面。

ci 命令

ci 命令将源文件的当前版本“签入”( check int )到 RCS 中去:

$ ci important.c

这样文件 important.c 就会被删除掉,而其内容和控制信息都已经被保存到 RCS 文件 important.c,v 中了。

如果不想被删除,就加上 -l 命令,这样 RCS 文件将被锁定。

co 命令

如果想修改文件,就必须“签出”( check out )该文件。如果不加 -l 参数,则签出的文件是只读的,否则可以签出一份可写文件,并且 RCS 文件将被锁定。

$ co -l important.c

rlog 命令

rlog 命令可以查看一个文件的改动摘要:

$ rlog important.c

如果想取出文件的某一个版本,可以指定其版本号:

$  co -r1.1 important.c

ci 命令也有一个 -r 选项,作用是强制指定主版本号,例如命令 ci -r2 important.c 将文件签入为版本2.1 ,RCS 默认使用数字1作为次版本号。

rcsdiff 命令

可以用 rcsdiff 命令了解两个版本之间的区别:

$ rcsdiff -r1.1 -r1.2 important.c

标识版本

RCS 系统可以在源文件中使用一些特殊的字符串(宏)来帮助跟踪文件所做的改动,最常用的是 $RCSfile$ 和 $Id$ 。

$RCSfile$ 将扩展为该文件的名字,$Id$ 将扩展为比一个标识版本号的字符串。这些宏将在签出时被扩展,并且在签入时自动更新。

GNU make 和 RCS

GNU make 内置了一些用于管理 RCS 文件的规则。如果没有源文件, make 可以从 RCS 文件中去构建:

$ rm -f important.c
$ make important

使用最新版本的 important.c 成功构建。

ident 命令

ident 命令查找包含 $Id$ 字符串的文件的版本。

$ ident important

CVS

与 RCS 相比, CVS 系统有一个明显的优势:人们可以通过互联网使用 CVS 系统,而不像 RCS 系统只能用在一个共享的本地目录中。 CVS 还支持并行开发,即许多程序员可以同一时间修改同一个文件,而 RCS 在任一时间只允许一个用户修改一个特定文件。

首先要创建一个版本库, CVS 系统将其控制文件和它管理的文件的主副本保存在这个版本库中。版本库的结构是树状的。

将新建的目录初始为 CVS 版本库:

$ mkdir -p /home/diwen/cvs_repo
$ cvs -d /home/diwen/cvs_repo init

所有的 cvs 命令在查找 CVS 目录时都可以使用两种方法:一是在命令行中使用 -d <path> 选项,如果未使用 -d 选项, cvs 命令就去查看环境变量 CVSROOT 的值。可以这样设置它:

$ export CVSROOT=/home/diwen/cvs_repo

接下来导入项目,切换到项目目录,用下面的命令将它们全部导入到版本库中:

$ cd TmpProject
$ cvs import -m "Init" diwen/TmpProject diwen start

-m "Init" 表示日志信息。

diwen/TmpProject 告诉在版本库中的保存位置, diwen 相当于开发者标签, start 是一个版本标签。

使用下面的命令将版本库导出为一个副本:

$ cd tmp
$ cvs checkout diwen/TmpProject

在导出的副本中,输入下面的命令查看版本的改动:

$ cvs diff

使用下面的命令提交:

$ cvs commit

它会启动一个编辑器让你输入一条日志。

询问自某一版本开始的改动情况:

$ cvs rdiff -r1.1 diwen/TmpProject

用下面的命令更新版本库的副本:

$ cvs update -Pd diwen/TmpProject

跨网络访问 CVS

如果要跨网络操作,只需要修改一下环境变量 CVSROOT 即可。

比如,可以修改成 :pserver:anonymous@dev/w3/org:/sources/public,该版本库使用密码验证( pserver ),且位于服务器 dev.w3.org 上。

Note

这个地址可以直接被访问到。使用 密码 anonymous

然后输入 cvs login 登陆。

之后就可以使用 cvs 命令了。用法和之前的一样,只有一个小的区别,需要给每个 cvs 命令加上 -z3 选项以强制执行数据压缩,这样可以节约网络带宽。

$ cvs -z3 checkout validator

如何设置 CVS 服务器书中并无详细介绍,需要查阅资料。

CVS 还有前端的图形界面程序,但书中介绍的网站已经找不到这个程序了。

编写手册页

大多数手册业基本都由以下几部分组成:

UNIX 手册页是通过工具 nroff 排版的,在多多数 Linux 系统中,相同的工具是 groff 。

书中给出了一个手册页的源码例子,通过它可以生成手册页的说明。

将这个源码放到具体的 manpage 目录,就可以通过 man 命令来查询了。

在我的 CentOS 7 上面,这个目录是 /usr/share/man

生成说明的命令是:

$ groff -Tascii -man myapp.1

myapp 即是说明,.1代表了手册页第一页。

发行软件

patch 程序

patch 命令允许软件的开发者只发行定义两个版本之间区别的文件,这样无论是谁,只要他拥有某个文件的第一个版本和第一个版本与第二个版本之间的区别文件,他就可以用 patch 命令来自己生成该文件的第二个版本。

这个区别的文件可以由 diff 命令来生成:

$ diff file1 file2 > diffs

然后就可以用 patch 命令更新 file1 ,从而将其更新为 file2 :

$ patch file1 diffs

如果想回滚刚才的修改,只需要使用 -R 选项:

$ patch -R file1 diffs

其他软件发行工具

Linux 程序和源代码通常以打包压缩的格式发行,在文件名中包含软件的版本号,文件的后缀名为 .tar.gz 或 .tgz ,这类文件通常也被称为 tarballs 文件。

下面的命令将为应用程序创建一个打包压缩文件:

$ tar cvf myqpp-1.0.tar main.c 2.c 3.c *.h myapp.1 Makefile5

可以用压缩程序 gzip 对该文件进行压缩,使得其容量更小:

$ gzip myapp-1.0.tar

最终生成一个 myapp-1.0.tar.gz 文件。

解包的过程是:

$ gzip -d myapp-1.0.tar.gz
$ tar xvf myapp-1.0.tar

若是使用 GNU 版本的 tar 命令,则可以一步到位:

$ tar zcvf myapp_v1.tgz main.c 2.c 3.c *.h myapp.1 Makefile5

解压缩的命令是:

$ tar zxvf myapp_v1.tgz

tar 命令的基本语法是:

tar [options] [list of files]

它可以有下面的选项组合:

RPM 软件包

RPM 的优点有:使用广泛、能够只用一条命令来安装软件包、只需要处理一个文件、自动的依赖关系检查、由最干净的源代码而来,从而可以重新编译。

使用 RPM 软件包文件

每个 RPM 软件包都存储在一个以 .rpm 为后缀名的文件中。它通常遵循一种命名规范:

name-version-release.architecture.rpm

name 指定软件包的通用名称, version 指定版本号, release 包含一个数字,包含软件包的 RPM 版本号, architecture 是程序的架构,如果是 noarch 表示不针对某个特定的架构,如果是 src 表示软件包为源代码包。

安装 RPM 软件包

使用如:

$ rpm -Uhv MySQL-server-5.0.41-0.glibc23.i386.rpm

你可以询问某个软件包是否已安装:

$ rpm -qa mariadb

创建 RPM 软件包

可以用命令 rpmbuild 来创建一个 RPM 软件包。详细参考书本。

开发环境

简要介绍了 KDevelop IDE 。